/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.caf.data.jpa.repository.support;

import io.iec.caf.data.jpa.repository.CafI18nColumnDict;
import io.iec.caf.data.jpa.repository.CafI18nEntity;
import io.iec.caf.data.jpa.repository.CafJpaRepository;
import io.iec.caf.data.jpa.repository.support.event.CafJpaRepositoryEvent;
import io.iec.caf.data.jpa.repository.support.event.CafJpaRepositoryEventArgs;
import io.iec.caf.data.jpa.repository.support.event.CafJpaRepositoryPublish;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.hibernate.metamodel.internal.MetamodelImpl;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.PersistersContext;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.Tuple;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * This is {@link CafJpaRepositoryImpl}.
 *
 * @author yisiqi
 * @since 1.0.0
 */
@Slf4j
@Transactional(readOnly = true)
class CafJpaRepositoryImpl<E, ID extends Serializable> extends SimpleJpaRepository<E, Serializable> implements CafJpaRepository<E, Serializable> {


    private final JpaEntityInformation<E, ID> entityInformation;
    private final EntityManager entityManager;
    private final MetamodelImpl metamodel;


    public CafJpaRepositoryImpl(JpaEntityInformation<E, ID> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityInformation = entityInformation;
        this.entityManager = entityManager;
        if (!(entityManager.getMetamodel() instanceof MetamodelImpl)) {
            throw new UnsupportedOperationException();
        }
        this.metamodel = (MetamodelImpl) entityManager.getMetamodel();
    }


    /*@Override
    @Transactional
    public <T extends E> T saveI18nEntity(T entity, List<CafI18nPropertyValue> values) {
        // EntityPersister persister = metamodel.entityPersister(entity.getClass());
        // AbstractEntityPersister abstractEntityPersister = (AbstractEntityPersister) persister;
        // abstractEntityPersister.processInsertGeneratedProperties();
        if (entityInformation.isNew(entity)) {
            entityManager.persist(entity);
            return entity;
        } else {
            return entityManager.merge(entity);
        }
    }*/

    @Override
    @Transactional
    public <T extends E> T save(T entity, CafI18nColumnDict dict) {
        T persistedEntity = this.save(entity);
        ID id = entityInformation.getId(entity);
        if (dict != null && !dict.getAllColumnsDict().isEmpty()) {
            AbstractEntityPersister persister = (AbstractEntityPersister) metamodel.entityPersister(entity.getClass().getCanonicalName());
            persister.getRootTableName();

            StringBuilder builder = new StringBuilder("update ")
                    .append(persister.getRootTableName()).append(" ")
                    .append("set ");
            // update clause
            Map<String, String> params = dict.getAllColumnsDict();
            int paramCount = params.size();
            int count = 0;
            for (Map.Entry<String, String> entry : params.entrySet()) {
                count++;
                if (count == paramCount) {
                    builder.append(" ").append(entry.getKey()).append(" = :").append(entry.getKey());
                } else {
                    builder.append(" ").append(entry.getKey()).append(" = :").append(entry.getKey()).append(",");
                }

            }
            // where clause
            String[] idNames = persister.getIdentifierColumnNames();
            builder.append(" where ").append(idNames[0]).append(" = :id");
            // bind parameters value
            Query query = entityManager.createNativeQuery(builder.toString(), entity.getClass());
            for (Map.Entry<String, String> entry : params.entrySet()) {
                query.setParameter(entry.getKey(), entry.getValue());
            }
            // bind id value
            query.setParameter("id", id);
            query.executeUpdate();
        }

        var jpaRepoEvent = SpringBeanUtils.getBean(CafJpaRepositoryPublish.class);
        if(jpaRepoEvent!=null){
            CafJpaRepositoryEventArgs args = new CafJpaRepositoryEventArgs(dict.getAllColumnsDict(),entity,dict);
            CafJpaRepositoryEvent event = new CafJpaRepositoryEvent(args);
            jpaRepoEvent.onHandleCafJpaRepoSave(event);
        }


        return persistedEntity;
    }



    @SuppressWarnings("unchecked")
    @Override
    public Optional<CafI18nEntity<E>> findByIdWithAllI18nColumn(Serializable id) {
        Optional<E> persistedEntity = findById(id);

        Class<E> entityClass = entityInformation.getJavaType();
        AbstractEntityPersister persister = (AbstractEntityPersister) metamodel.entityPersister(entityClass);

        List<String> columns = PersistersContext.getI18nEntityColumnsMap().get(entityClass.getCanonicalName());
//        List<String> langs = PersistersContext.getCurrentLangList();
        Map<String,String> langs = CafJpaLanguageThreadHolder.getLanguages();

        if (persistedEntity.isPresent() && (CollectionUtils.isEmpty(columns) || CollectionUtils.isEmpty(langs))) {
            log.warn(CollectionUtils.isEmpty(columns) ? "Can not find any i18n columns." : "Can not find any languages.");
            return Optional.of(CafI18nEntity.<E>builder().entity(persistedEntity.get()).build());
        } else if (!persistedEntity.isPresent()) {
            return Optional.empty();
        }

        StringBuilder sqlBuilder = new StringBuilder().append("select ");
        String[] idNames = persister.getIdentifierColumnNames();
        columns.forEach((c) -> langs.entrySet().forEach((l) ->
                sqlBuilder.append(c).append("_").append(l.getValue().replaceAll("^_", "")).append(", ")));
        sqlBuilder
                .deleteCharAt(sqlBuilder.length() - 2)
                .append("from ").append(persister.getRootTableName())
                .append(" where ").append(idNames[0]).append(" = :id");
        List<Tuple> tuples = entityManager
                .createNativeQuery(sqlBuilder.toString(), Tuple.class)
                .setParameter("id", id)
                .getResultList();
        Object[] values = tuples.get(0).toArray();

        CafI18nColumnDict dict = new CafI18nColumnDict();
        for (int i = 0; i < columns.size(); i++) {
            String col = columns.get(i);
            int j = 0;
            for (Map.Entry<String, String> entry : langs.entrySet()) {
                var value = values[i * langs.size() + j];
                if (value != null)
                    dict.put(col, entry.getKey(), String.valueOf(value));
                j++;
            }
        }

        CafI18nEntity<E> entity = CafI18nEntity.<E>builder()
                .entity(persistedEntity.get())
                .dict(dict)
                .build();
        return Optional.of(entity);
    }
}
